//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using OpenLS.Spreadsheet.Collections;

namespace OpenLS.Spreadsheet
{
    class NonVolatileCellBag : IEnumerable<Cell>, INotifyCollectionChanged, INotifyPropertyChanged
    {
        public NonVolatileCellBag(Worksheet ws)
        {
            _worksheet = ws;
        }
        private readonly Worksheet _worksheet;
        public Worksheet Worksheet
        {
            get { return _worksheet; }
        }

        public IEnumerator<Cell> GetEnumerator()
        {
            var cellWithSpanTable = new Dictionary<CellAddress, Cell>(new CellAddressComparer());
            foreach (Cell cell in _table.Values)
            {
                if (!cell.HasSpan)
                {
                    yield return cell;
                    continue;
                }
                if (!cellWithSpanTable.ContainsKey(cell.Address))
                    cellWithSpanTable.Add(cell.Address, cell);
            }
            foreach (Cell cell in cellWithSpanTable.Values)
                yield return cell;
        }
        public void Add(Cell cell)
        {
            //Debug.WriteLine("Adding cell " + cell.Value);
            CellAddress ca = cell.AddressUnsafe;
            if (cell.IsSingle)
                internalAddCell(ca, cell);
            else
            {
                foreach (CellAddress a in new RangeAddress(ca, cell.GetBottomRight()).GetCellAddresses())
                    internalAddCell(a, cell);
            }


            onCellAdded(cell);
        }

        public void AddNoRow(Cell cell)
        {
            if (cell.ColumnSpan == 1 && cell.RowSpan == 1)
            {
                internalAddCellNoRow(cell.Address, cell);
                return;
            }
            CellAddress ca = cell.AddressUnsafe;
            foreach (CellAddress a in new RangeAddress(ca, cell.GetBottomRight()).GetCellAddresses())
                internalAddCellNoRow(a, cell);
        }
        public void Remove(Cell cell)
        {
            Contract.Requires(cell != null);
            Contract.Requires(!cell.IsVolatile);
            Contract.Ensures(!Contains(cell));
            CellAddress originalAddress = cell.Address;
            foreach (CellAddress a in cell.GetRangeAddress().GetCellAddresses())
            {
                if (_table.ContainsKey(a))
                    _table.Remove(a);
            }
            if (!this._inEdit)
            {
                if (CollectionChanged != null)
                {
                    CollectionChanged(this,
                                      new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cell, 0));
                    CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                }
             //   Contract.Assert(!this.Worksheet.TopLeftPane.Cells.Contains(cell));
            }
            if (Worksheet.BottomRight.Equals(originalAddress))
            {
                Worksheet.InvalidateBottomRight();
            }
         //   Contract.Assert(_inEdit || !this.Worksheet.TopLeftPane.Cells.Contains(cell));
        }

        private void onCellAdded(Cell cell)
        {
            Contract.Requires(cell != null);
            if (_inEdit)
                return;
            if (this._worksheet.CellAddedEventDisabled)
                return;
            if (CollectionChanged != null)
            {
                //\\CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, cell));
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, cell));
            }
            if (Worksheet.BottomRight != null)
            {
                if (cell.Address.Column > Worksheet.BottomRight.Column || cell.Address.Row > Worksheet.BottomRight.Row)
                {
                    Worksheet.BottomRight = new CellAddress(Math.Max(Worksheet.BottomRight.Row, cell.Address.Row), Math.Max(Worksheet.BottomRight.Column, cell.Address.Column));
                    Worksheet.ChangeBottomRight();
                }
            }
            if (cell.Column.Index > _worksheet.Right)
            {
                Worksheet.Right = Math.Max(Worksheet.Right, cell.Column.Index);
            }
            if (cell.Row.Index > _worksheet.Bottom)
            {
                Worksheet.Bottom = Math.Max(Worksheet.Bottom, cell.Row.Index);
            }
           // Debug.WriteLine("Adding cell " + cell.Label);
            _worksheet.OnCellAdded(cell);

        }
        void internalAddCellNoRow(CellAddress address, Cell c)
        {
            internalAddCell(address, c);
            if (CollectionChanged != null)
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, c));
        }
            
        void internalAddCell(CellAddress address, Cell cell)
        {
            Contract.Requires(cell != null);
         //   Debug.WriteLine(string.Format("Adding {0} {1}", address, cell.Label));
            _table.Add(address, cell);
        }
        CellTable _table = new CellTable();
        private bool _inEdit;

        class CellTable
        {
            struct CellClusterAddress
            {
                private int row;
                private int column;
                public const int mult = 8;

                public CellClusterAddress(CellAddress address)
                {
                    this.row = address.Row/mult;
                    this.column = address.Column/mult;
                }

                public int Key
                {
                    get {
                        Contract.Assert((column < 8192));
                        Contract.Assert((column < 1100000 / 8));
                        return column | row << 14;
                    }
                }

                public override bool Equals(object obj)
                {
                    if (ReferenceEquals(null, obj)) return false;
                    if (obj.GetType() != typeof (CellClusterAddress)) return false;
                    return Equals((CellClusterAddress) obj);
                }

                public bool Equals(CellClusterAddress other)
                {
                    return other.row == row && other.column == column;
                }

                public override int GetHashCode()
                {
                    unchecked
                    {
                        return (row*397) ^ column;
                    }
                }

                public CellAddress ToCellAddress()
                {
                    return new CellAddress(row * mult, column * mult);
                }
#if DEBUG
                public static CellClusterAddress FromKey(int key)
                {
Contract.Ensures(Contract.Result<CellClusterAddress>().Key == key);
                    int row = key >> 14;
                    int column = key & 0x3FFF;
                    return new CellClusterAddress(){column= column, row = row};

                }
#endif
            }
            readonly CachedDictionary<Cell[,]> _innerTable = new CachedDictionary<Cell[,]>();
            private int _count;
            public int Count { get
            {
                Contract.Assert(_count == Enumerable.Count(this.Values));
                return _count;
            } }
#if DEBUG
            public IEnumerable<CellAddress> Keys
            {
                get
                {
                    var clusterKeys = _innerTable.Keys;
                    foreach (var key in clusterKeys)
                    {
                        CellClusterAddress aa = CellClusterAddress.FromKey(key);
                        CellAddress a = aa.ToCellAddress();
                        var cluster = _innerTable[key];
                        for (int row = 0; row < CellClusterAddress.mult; row++)
                            for (int column = 0; column < CellClusterAddress.mult; column++)
                            {
                                var c = cluster[row, column];
                                if (c != null)
                                    yield return a + new CellRelativeAddress(row, column);

                            }

                    }
                }
            }
#endif
            public IEnumerable<Cell> Values
            {
                get {
                    IEnumerable<Cell[,]> clusters = _innerTable.Values;
                    foreach (var cluster in clusters)
                    {
                        for (int row = 0; row < CellClusterAddress.mult; row++)
                            for (int column = 0; column < CellClusterAddress.mult; column++)
                            {
                                var c = cluster[row, column];
                                if (c != null)
                                    yield return c;

                            }
                    }
                }
            }
            static void GetCusterAddress (CellAddress cellAddress, out CellClusterAddress clusterAddress, out int row, out int column)
            {
                clusterAddress = new CellClusterAddress(cellAddress);
                row = cellAddress.Row % CellClusterAddress.mult;
                column = cellAddress.Column % CellClusterAddress.mult;
            }
            public bool TryGetValue(CellAddress cellAddress, out Cell result)
            {
                CellClusterAddress a;
                int row;
                int column;
                GetCusterAddress(cellAddress, out a, out row, out column);
                Cell[,] value;
                if (!_innerTable.TryGetValue(a.Key, out value))
                {
                    result = null;
                    return false;
                }
                result = value[row, column];
                Contract.Assert(result == null || result.GetRangeAddress().Contains(cellAddress));
                return result != null;
            }

            public void Remove(CellAddress address)
            {
                CellClusterAddress clusterAddress;
                int row;
                int column;
                GetCusterAddress(address, out clusterAddress, out row, out column);
                Cell[,] value;
                if (!_innerTable.TryGetValue(clusterAddress.Key, out value))
                    return;
                Contract.Assert(value[row, column] != null);
                value[row, column] = null;
                _count--;

            }

            public Cell this[CellAddress address]
            {
                get
                {
                    CellClusterAddress clusterAddress;
                    int row;
                    int column;
                    GetCusterAddress(address, out clusterAddress, out row, out column);
                    Cell[,] cells;
                    if (!_innerTable.TryGetValue(clusterAddress.Key, out cells))
                        return null;
                    return cells[row, column];
                }
            }

            public void Add(CellAddress address, Cell cell)
            {
                Contract.Ensures(this[address] == cell);
                CellClusterAddress  clusterAddress;
                int row;
                int column;
                GetCusterAddress(address, out clusterAddress, out row, out column);
                Cell[,] cells;
                if (!_innerTable.TryGetValue(clusterAddress.Key, out cells))
                {
                    cells = new Cell[CellClusterAddress.mult, CellClusterAddress.mult];
                    _innerTable.Add(clusterAddress.Key, cells);
                }
                // SJE FIX 11-07. It seems an issue.
                // Contract.Assert(cells[row, column] == null);
                cells[row, column] = cell;
                _count++;
            }

            public bool ContainsKey(CellAddress cellAddress)
            {
                CellClusterAddress cellClusterAddress;
                int row;
                int column;
                GetCusterAddress(cellAddress, out cellClusterAddress, out row, out column);
                Cell[,] value;
                if (!_innerTable.TryGetValue(cellClusterAddress.Key, out value))
                    return false;
                return value[row, column] != null;
            }
        }

        internal Cell GetItemIfAny(CellAddress a)
        {
            Contract.Ensures(Contract.Result<Cell>() == null || Contract.Result<Cell>().GetRangeAddress().Contains(a));
            Cell result;
            if (!_table.TryGetValue(a, out result))
                return null;
            return result;
        }

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region INotifyCollectionChanged Members

        /// <summary>
        /// Occurs when the collection changes.
        /// </summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        internal void NotifyCollectionChanged (NotifyCollectionChangedEventArgs args)
        {
            var c = CollectionChanged;
            if (c != null)
                c(this, args);
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        internal int Count
        {
            get
            {
                return _table.Count;
            }
        }

#if DEBUG
        internal bool ValidAddresses
        {
            get {
                if (_table.Count > 1000)
                    return true;
                foreach (var address in this._table.Keys)
                {
                    if (Enumerable.All(_table[address].GetRangeAddress().GetCellAddresses(), a => !a.Equals(address)))
                    {
                        // Contract.Assert(false);
                        return false;
                    }
                }
                return true;
            }
       
        }
#endif

        internal  void BeginEdit()
        {
            _inEdit = true;
        }

        internal  void EndEdit()
        {
            _inEdit = false;
        }

        internal bool Contains(Cell cell)
        {
            return GetItemIfAny(cell.Address) == cell;
        }

        public void UpdateCellAddress(Cell cell, IEnumerable<CellAddress> oldAddresses, IEnumerable<CellAddress> newAddresses)
        {
            Contract.Requires(oldAddresses != null);
            Contract.Requires(newAddresses != null);
            foreach (var address in oldAddresses)
            {
                _table.Remove(address);
            }
            foreach (var address in newAddresses)
            {
                internalAddCell(address, cell);

            }
        }
    }
}
